#include "FiveWin.ch"
#include "xBrowse.ch"

memvar oIDE

//----------------------------------------------------------------------------//

CLASS TInspector FROM TWindow

   DATA   oForm         // Current form being inspected
   DATA   oInspected    // Current form or control being inspected
   DATA   aInspect AS ARRAY INIT {} // Array with all objects inspected

   DATA   oCombo, oBrw, oTabs

   CLASSDATA lRegistered AS LOGICAL

   METHOD New( oForm ) CONSTRUCTOR

   METHOD Close() //INLINE ::bValid := nil, ::End() //daniel

   METHOD SetForm( oForm )

   METHOD SetObject( oObject )
   
   METHOD ShowEvents()
   METHOD ShowProps()

   METHOD EditData()
   METHOD EditMethod()

ENDCLASS

//----------------------------------------------------------------------------//

METHOD New( oForm ) CLASS TInspector

   local oBrw, oCol, cItem := "", hWndTaskBar, lTaskBarVisible
   local oFont, aProps := GetProperties( oForm )

   DEFAULT cItem := "oForm1"

//   ::oInspected  = oForm

   #ifdef __XPP__
      DEFAULT ::lRegistered := .f.
   #endif

   Super:New( 7, 0, 46, 28, "Object Inspector",,,,,,,,,,,,,,.t., .f. )

   hWndTaskBar = FindWindow( "Shell_TrayWnd", "" )
   if hWndTaskBar != 0
      lTaskBarVisible = WndTop( hWndTaskBar ) <= GetSysMetrics( 1 ) - WndHeight( hWndTaskBar )
   else   
      lTaskBarVisible = .F.
   endif   
   ::nHeight = GetSysMetrics( 1 ) - oIde:oWndMain:nHeight - ;
               If( lTaskBarVisible, WndHeight( hWndTaskBar ), 0 )

   ::SetColor( "N/W*" )

   DEFINE FONT oFont NAME "Ms Sans Serif" SIZE 0, -8

   ::oInspected = oForm

   @ 28, 3 XBROWSE ::oBrw ;
      FIELDS ::oBrw:aRow:cName, { |x| ::oBrw:aRow:Value( x ) } ;
      HEADERS 'Property', 'Value' ;
      COLSIZES 80, 101 ;
      ARRAY aProps OF Self ;
      PIXEL SIZE 203, ::nHeight - 82 ;
      ON CHANGE SetEditType( ::oBrw )

   ::oBrw:bClrStd          = { || If( ( ::oBrw:nArrayAt % 2 ) == 0, { CLR_BLACK, RGB( 224, 236, 255 ) }, { CLR_BLACK, RGB( 189, 211, 253 ) } ) }
   ::oBrw:bClrSel          = { || If( ( ::oBrw:nArrayAt % 2 ) == 0, { CLR_BLACK, RGB( 224, 236, 255 ) }, { CLR_BLACK, RGB( 189, 211, 253 ) } ) }
   ::oBrw:nMarqueeStyle    = MARQSTYLE_HIGHLROW
   ::oBrw:nColDividerStyle = LINESTYLE_DARKGRAY
   ::oBrw:aCols[ 1 ]:bLDClickData = { || ::oBrw:aCols[ 2 ]:Edit() }
   ::oBrw:oCol( "Property" ):bLDClickData := { || ::oBrw:Value:Edit() }
   ::oBrw:lRecordSelector = .F.

   ::oBrw:CreateFromCode() 
   ::oBrw:SetColor( 0, RGB( 224, 236, 255 ) )

   @ 3, 3 COMBOBOX ::oCombo VAR cItem FONT oFont ;
      ITEMS ::aInspect OF Self PIXEL SIZE 203, 100 ;
      ON CHANGE If( ::nAt == 1, ::oWnd:oForm:SetFocus(),;
                If( ::nAt != 0, ::oWnd:oForm:aControls[ ::nAt - 1 ]:SetFocus(),) )

   ::oCombo:Set3DLook()
   ::oCombo:Refresh()

   @ 274, 0 TABS ::oTabs ;
      PROMPTS "&Properties", "&Events" OF Self PIXEL ;
      ACTION  If( nOption == 1, ::ShowProps(), ::ShowEvents() )

   ::oTabs:nAlign = 4

   ::ToolWindow()
   ::Show()
   ::bValid   = { || ::Hide(), .f. } // We don't let this window be closed
   ::bResized = { | nSizeType, nWidth, nHeight | ::oBrw:nWidth := nWidth - 6,;
                  ::oBrw:nHeight := nHeight - 48, ::oCombo:nWidth := nWidth - 6 }
                  
return Self

//----------------------------------------------------------------------------//

METHOD ShowProps() CLASS TInspector

   ::oBrw:aCols[ 1 ]:cHeader = "Property"
   ::oBrw:aCols[ 2 ]:cHeader = "Value"
   ::oBrw:SetArray( GetProperties( ::oInspected ) )
   ::oBrw:GoTop()

return nil

//----------------------------------------------------------------------------//

METHOD ShowEvents() CLASS TInspector

   ::oBrw:aCols[ 1 ]:cHeader = "Event"
   ::oBrw:aCols[ 2 ]:cHeader = "Method"
   ::oBrw:nArrayAt = 1
   ::oBrw:SetArray( GetEvents( ::oInspected ) )
   ::oBrw:GoTop()

return nil

//----------------------------------------------------------------------------//

METHOD EditData() CLASS TInspector

   local cData    := ::oInspect:Property( ::oBrw:nAt )
   local bInspect := ::oInspect:Inspect( cData )
   local aArray   := If( ValType( bInspect ) == "A", bInspect, nil )
   local uTemp    := OSend( ::oInspect, cData )

   DEFAULT uTemp := ""

   if ValType( uTemp ) == "C"
      uTemp = PadR( uTemp, 50 )
   endif

   if ::oBrw:lEditCol( 2, @uTemp,,,,, aArray, bInspect )
      if ValType( uTemp ) == "C"
         uTemp = RTrim( uTemp )
      endif
      OSend( ::oInspect, "_" + cData, uTemp )
   endif

   ::oBrw:DrawSelect()
   ::oInspect:Refresh()

return nil

//----------------------------------------------------------------------------//

METHOD EditMethod() CLASS TInspector

   local cEvent := ::oInspect:Event( ::oBrw:nAt )

   OSend( ::oForm, "_" + cEvent, "Frm" + SubStr( cEvent, 3 ) )
   ::oBrw:DrawSelect()
   oIDE:oWndCode:EditMethod( "Frm" + SubStr( cEvent, 3 ), ::oForm:cClassName,;
                             ::oInspect:aEvents[ ::oBrw:nAt ] )

return nil

//----------------------------------------------------------------------------//

METHOD SetForm( oForm ) CLASS TInspector

   local aValues

   if ! ::oForm == oForm .or. ! ::oInspected == oForm
      oForm:oInspector = Self
      ::oForm := ::oInspected := oForm
      ::oCombo:SetItems( oForm:GetItems() )
      ::oCombo:Set( oForm:cVarName + " AS " + oForm:cClassName )
   endif

   aValues = If( ::oTabs:nOption ==1, GetProperties( oForm ), GetEvents( oForm ) )
   if oForm:nLeft <= 0 
   	oForm:nLeft = 0
   endif
   if oForm:nTop <= 0 
   	oForm:nTop = 0
   endif
   if ( oForm:nTop + oForm:nHeight ) >= GetSysMetrics( 1 )
   	oForm:nTop = GetSysMetrics( 1 ) - oForm:nHeight 
   endif

   if ( oForm:nLeft + oForm:nWidth ) >= GetSysMetrics( 0 )
   	oForm:nLeft = GetSysMetrics( 0 ) - oForm:nWidth 
   endif


   ::oBrw:SetArray( aValues )
   ::oBrw:GoTop()

return nil

//----------------------------------------------------------------------------//

METHOD SetObject( oObject ) CLASS TInspector

   local aValues

   if ! ::oInspected == oObject
      ::oInspected = oObject
      ::oCombo:Set( oObject:cVarName + " AS " + oObject:ClassName() )
   endif

   aValues = If( ::oTabs:nOption ==1, GetProperties( oObject ), GetEvents( oObject ) )
   ::oBrw:SetArray( aValues )
   ::oBrw:GoTop()

return nil

//----------------------------------------------------------------------------//

METHOD Close( ) CLASS TInspector
   ::bValid := nil
   ::end()
return nil

static function ValToStr( uVal )

   local nType := ValType( uVal )

   do case
      case nType == "C"
           return uVal

      case nType == "N"
           return Str( uVal )

      case nType == "L"
           return If( uVal, "True", "False" )

      case nType == "D"
           return DToC( uVal )

      case nType == "O"
           return uVal:ClassName()

      case nType == "A"
           return "{...}"

      otherwise
           return "nil"
   endcase

return nil

//----------------------------------------------------------------------------//

CLASS TProperty

   DATA   oObject
   DATA   cName
   DATA   bValues
   DATA   nEditType
   DATA   bOnRead
   DATA   bOnWrite
   DATA   bEdit
   DATA   aEvent

   METHOD New() INLINE Self
   METHOD GetLabel( nIndex ) INLINE If( ! Empty( nIndex ), Eval( ::bValues )[ nIndex ], Space( 30 ) )
   METHOD GetIndex( uValue ) INLINE AScan( Eval( ::bValues ), uValue )
   METHOD Value( x ) SETGET

ENDCLASS

METHOD Value( x ) CLASS TProperty

   if x != nil .and. ValType( ::bOnWrite ) == "B"
      Eval( ::bOnWrite, Self, x )
   endif

return If( ValType( ::bOnRead ) == "B", Eval( ::bOnRead, Self ), Space( 30 ) )

//----------------------------------------------------------------------------//


function GetEvents( o )

   local aEvents := {}, n, oProp, cProp

   if o != nil
   
      for n = 1 to o:EveCount()
         AAdd( aEvents, oProp := TProperty():New() )
         oProp:oObject   = o

         cProp       = o:Event( n )[ 1 ]
         oProp:cName = cProp
         
         oProp:nEditType = EDIT_BUTTON
         oProp:bOnRead   = { | oProp | If( o != nil, PadR( OSend( o, oProp:cName ), 30 ), Space( 30 ) ) }
         oProp:bOnWrite  = { | oProp, uValue | OSend( o, "_" + oProp:cName, AllTrim( uValue ) ), oIde:oWndCode:EditMethod( OSend( o, oProp:cName ), If( o:IsDerivedFrom( TForm() ), o:cClassName, o:oWnd:cClassName ), oProp:aEvent ) }
         oProp:bEdit     = { | nRow, nCol, oCol | SubStr( o:cVarName, 2 ) + SubStr( oCol:oBrw:aRow:cName, 3 ) }
         oProp:aEvent    = o:Event( n )
      next   
   else   
      AAdd( aEvents, oProp := TProperty():New() )
      oProp:cName     = "Not defined"
      oProp:nEditType = EDIT_GET
      oProp:bOnRead   = { || Space( 30 ) }
   endif   

return aEvents

//----------------------------------------------------------------------------//

function GetProperties( o )

   local aProps := {}, n, oProp, cProp

   if o != nil
   
      for n = 1 to o:PropCount()
         AAdd( aProps, oProp := TProperty():New() )
         oProp:oObject   = o

         cProp       = o:Property( n )
         oProp:cName = cProp
 
         do case
            case cProp == "aControls"
                 oProp:nEditType = 0
                 oProp:bOnRead   = { | oProp | PadR( "Array(" + AllTrim( Str( Len( o:aControls ) ) ) + ")", 30 ) }
                 oProp:bOnWrite  = { | oProp, uValue | nil }

            case cProp == "aItems"
                 oProp:nEditType = EDIT_BUTTON
                 oProp:bOnRead   = { | oProp | PadR( "Array(" + AllTrim( Str( Len( o:aItems ) ) ) + ")", 30 ) }
                 oProp:bOnWrite  = { | oProp, uValue | nil }
                 oProp:bEdit     = { | c | o:SetItems( EditItems( o:aItems ) ), o:aItems }
                 
            case cProp == "cBmpFile"
                 oProp:nEditType = EDIT_BUTTON
                 oProp:bOnRead   = { | oProp | PadR( o:cBmpFile, 30 ) }
                 oProp:bOnWrite  = { | oProp, uValue | OSend( o, "_" + oProp:cName, AllTrim( uValue ) ) }
                 oProp:bEdit     = { | c | c := cGetFile( "*.bmp", "Select a BMP" ), If( ! Empty( c ), ( o:LoadBmp( c ), c ), "" ) }

            case cProp == "nAlign"
                 oProp:nEditType = EDIT_LISTBOX
                 oProp:bValues   = { || { "None", "Top", "Left", "Bottom", "Right", "Center" } }
                 oProp:bOnRead   = { | oProp | oProp:GetLabel( o:nAlign ) }
                 oProp:bOnWrite  = { | oProp, uValue | o:nAlign := oProp:GetIndex( uValue ), o:SetFocus() }

            case cProp == "nBorderStyle"
                 oProp:nEditType = EDIT_LISTBOX
                 oProp:bValues   = { || { "None", "Thin", "Resizing", "Dialog frame" } }
                 oProp:bOnRead   = { | oProp | oProp:GetLabel( o:nBorderStyle ) }
                 oProp:bOnWrite  = { | oProp, uValue | o:nBorderStyle := oProp:GetIndex( uValue ), o:SetFocus() }

            case cProp == "nClrPane" .or. cProp == "nClrText"
                 oProp:nEditType = EDIT_BUTTON
                 oProp:bOnRead   = { | oProp | cClrToCode( OSend( o, oProp:cName ) ) }
                 oProp:bOnWrite  = { | oProp, uValue | OSend( o, "_" + oProp:cName, uValue ), o:Refresh() }
                 oProp:bEdit     = { || ChooseColor( OSend( o, oProp:cName ) ) }

            case cProp == "nId"
                 oProp:nEditType = 0
                 oProp:bOnRead   = { | oProp | If( o != nil, PadR( OSend( o, oProp:cName ), 30 ), Space( 30 ) ) }
                 oProp:bOnWrite  = { | oProp, uValue | OSend( o, "_" + oProp:cName, AllTrim( uValue ) ) }

            case cProp == "nPosition"
                 oProp:nEditType = EDIT_LISTBOX
                 oProp:bValues   = { || { "Normal", "Maximized", "Minimized", "Centered" } }
                 oProp:bOnRead   = { | oProp | oProp:GetLabel( o:nPosition ) }
                 oProp:bOnWrite  = { | oProp, uValue | o:nPosition := oProp:GetIndex( uValue ) }

            case cProp == "nScrollBars"
                 oProp:nEditType = EDIT_LISTBOX
                 oProp:bValues   = { || { "None", "Vertical", "Horizontal", "Both" } }
                 oProp:bOnRead   = { | oProp | oProp:GetLabel( o:nScrollBars ) }
                 oProp:bOnWrite  = { | oProp, uValue | o:nScrollBars := oProp:GetIndex( uValue ) }

            case cProp == "oBrush"
                 oProp:nEditType = EDIT_BUTTON
                 oProp:bOnRead   = { | oProp | "(TBrush object)" }
                 oProp:bOnWrite  = { | oProp, uValue | o:Refresh() }
                 oProp:bEdit     = { || nil }

            case cProp == "oFont"
                 oProp:nEditType = EDIT_BUTTON
                 oProp:bOnRead   = { | oProp | "(TFont object)" }
                 oProp:bOnWrite  = { | oProp, uValue | o:Refresh() }
                 oProp:bEdit     = { || o:SelFont() }

            case cProp == "oMenu"
                 oProp:nEditType = If( ! Empty( o:oMenu ), EDIT_BUTTON, 0 )
                 oProp:bOnRead   = { | oProp | If( ! Empty( o:oMenu ), "(TMenu object)", "nil" ) }
                 oProp:bOnWrite  = { | oProp, uValue | o:Refresh() }
                 oProp:bEdit     = { || nil }
                 
            case Left( cProp, 1 ) == "l"     
                 oProp:nEditType = EDIT_LISTBOX
                 oProp:bValues   = { || { "Yes", "No" } }
                 oProp:bOnRead   = { | oProp | If( OSend( o, oProp:cName ), ".T.", ".F." ) }
                 oProp:bOnWrite  = { | oProp, uValue | OSend( o, "_" + oProp:cName, oProp:GetIndex( uValue ) == 1 ) }
        
            otherwise                      
                 oProp:nEditType = EDIT_GET
                 oProp:bOnRead   = { | oProp | If( o != nil, PadR( OSend( o, oProp:cName ), 30 ), space( 30 ) ) }
                 oProp:bOnWrite  = { | oProp, uValue | OSend( o, "_" + oProp:cName, AllTrim( uValue ) ) }
                 //?oProp:cName
         endcase        
      next   
   else   
      AAdd( aProps, oProp := TProperty():New() )
      oProp:cName     = "Not defined"
      oProp:nEditType = EDIT_GET
      oProp:bOnRead   = { || Space( 30 ) }
   endif   

return aProps

//----------------------------------------------------------------------------//

static function SetEditType( oBrw )

   local oProp     := oBrw:aRow
   local nEditType := oProp:nEditType
   local oCol      := oBrw:aCols[ 2 ]

   oCol:nEditType  := nEditType

   do case
      case nEditType == EDIT_GET
           oCol:aEditListTxt   = nil
           oCol:aEditListBound = nil

      case nEditType == EDIT_LISTBOX
           oCol:aEditListTxt   = Eval( oProp:bValues )
           oCol:aEditListBound = Eval( oProp:bValues ) // remove this if you want the index, not the string

      case nEditType == EDIT_BUTTON
           oCol:bEditBlock = oProp:bEdit // whatever is returned by the codeblock, it will be asigned
   endcase

return nil

//----------------------------------------------------------------------------//

function EditItems( aItems )

   local oDlg, cItems := "", n, oMemo
   
   for n = 1 to Len( aItems )
      cItems += cValToChar( aItems[ n ] ) + CRLF
   next   
   
   DEFINE DIALOG oDlg RESOURCE "EditItems"
   
   REDEFINE GET oMemo VAR cItems ID 100 OF oDlg MEMO
   
   oMemo:bGotFocus = { || oMemo:SetSel( 0, 0 ) }
   
   REDEFINE BUTTON ID 110 OF oDlg ;
      ACTION ( aItems := TextToArray( cItems ), oDlg:End() )
   
   ACTIVATE DIALOG oDlg CENTERED 
   
return aItems   

//----------------------------------------------------------------------------//

function TextToArray( cText )

   local aLines := Array( MLCount( cText ) ), n
   
   for n = 1 to Len( aLines )
      aLines[ n ] = RTrim( MemoLine( cText,, n ) )
   next
   
return aLines      
   
//----------------------------------------------------------------------------//